﻿using System;
using System.Drawing;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace IsreversI.GameLogic
{
    /// <summary>
    /// View keeping track of the current situation in the game; the score,
    /// whether the game is over, and if so, who has won, etc.
    /// </summary>
    class GameStateView : BoardModelAttachment
    {
        #region member variables
        public TileState[,] decorations;
        #endregion

        #region events
        public event BoardEventHandler GameOver;
        public event BoardEventHandler PlayerHasNoMoves;
        public event BoardEventHandler PossibleMovesUpdated;
        public event BoardEventHandler ScoreUpdated;
        #endregion

        #region properties
        public int RedPoints { get; private set; }
        public int BluePoints { get; private set; }
        #endregion

        #region constructors
        public GameStateView(BoardModel board)
        {
            this.acceptNewModel(this, new BoardEventArgs(board));
        }
        #endregion

        #region event handlers
        protected override void handleModelChange(BoardModel model)
        {
            base.handleModelChange(model);
            this.decorations = new TileState[model.BoardSize.Width, model.BoardSize.Height];
            this.BoardModel.StonePlayed += this.handleStoneAdded;
            this.BoardModel.StoneRemoved += this.handleStoneRemoved;
            this.BoardModel.StoneFlipped += this.handleStoneAdded;
            this.BoardModel.StoneFlipped += this.handleStoneRemoved;
            this.ensureScoreCorrectness();
            this.BoardModel.ModelUnlocked += this.handleModelUnlock;
            this.handleModelUnlock(this, new BoardEventArgs(model));
        }

        void handleModelUnlock(object sender, BoardEventArgs bea)
        {
            if (this.BoardModel.CurrentState != TurnState.GameOver)
                this.checkLegalMoves();
            else
                if (this.GameOver != null)
                    this.GameOver(this, bea);
        }

        void handleStoneAdded(object sender, BoardEventArgs bea)
        {
            this.givePoints(bea.NewState.OfPlayer(), 1);
            this.ScoreUpdated(this, bea);
        }

        void handleStoneRemoved(object sender, BoardEventArgs bea)
        {
            this.givePoints(bea.OldState.OfPlayer(), -1);
        }
        #endregion

        #region public methods
        public TileState DecoratedTileAt(Point p)
        {
            return DecoratedTileAt(p.X, p.Y);
        }

        public TileState DecoratedTileAt(int x, int y)
        {
            return this.BoardModel[x, y] | this.decorations[x, y];
        }

        /// <summary>
        /// Return true if the next player who's turn it is may play on the
        /// tile at the specified position.
        /// </summary>
        public bool LegalMove(Point position)
        {
            if (!this.DecoratedTileAt(position).HasFlag(TileState.MovePossible))
                return false;
            if (this.BoardModel.CurrentState == TurnState.GameOver)
                return false;
            return true;
        }

        /// <summary>
        /// Return true if the position given is within the board
        /// borders.
        /// </summary>
        public bool PointOnBoard(Point position)
        {
            return !(position.X < 0
                || position.Y < 0
                || position.X >= this.BoardModel.BoardSize.Width
                || position.Y >= this.BoardModel.BoardSize.Height);
        }
        
        public bool NextNPointsInDirectionAre(Point first, int count, IntVector2 direction, TileState state)
        {
            Point current = first + direction;
            while (count-- > 0)
            {
                if (!this.PointOnBoard(current))
                    return false;
                if (this.BoardModel[current] != state)
                    return false;
                current += direction;
            }
            return true;
        }

        /// <summary>
        /// Returns the distance to the next point in the specified direction that is
        /// in the specified, looking from (but not including) the origin.
        /// </summary>
        public int DistanceToNextPointInDirectionThatIs(Point origin, IntVector2 direction, TileState state)
        {
            // Don't count the point at the origin
            Point p = origin + direction;
            for (int i = 1; PointOnBoard(p); ++i)
            {
                if (this.BoardModel[p] == state)
                    return i;
                p += direction;
            }
            return -1; // No such point
        }
        #endregion

        #region private methods
        private void checkLegalMoves()
        {
            bool legalMovesPresent = false;
            for (int xC = 0; xC < decorations.GetLength(0); ++xC)
                for (int yC = 0; yC < decorations.GetLength(1); ++yC)
                {
                    TileState oldState = this.decorations[xC, yC];
                    if (this.checkMoveLegality(new Point(xC, yC)))
                        this.decorations[xC, yC] = oldState.WithSet(TileState.MovePossible);
                    else
                        this.decorations[xC, yC] = oldState.WithUnset(TileState.MovePossible);
                    if (this.decorations[xC, yC] != oldState)
                        if (this.PossibleMovesUpdated != null)
                            this.PossibleMovesUpdated(this, new BoardEventArgs(
                                this.BoardModel,
                                new Point(xC, yC),
                                this.BoardModel[xC, yC] | oldState,
                                this.DecoratedTileAt(new Point(xC, yC))
                            ));
                    if (this.decorations[xC, yC].HasFlag(TileState.MovePossible))
                        legalMovesPresent = true;
                }
            if (!legalMovesPresent)
                if (this.PlayerHasNoMoves != null)
                    this.PlayerHasNoMoves(this, new BoardEventArgs(this.BoardModel));
        }

        private bool checkMoveLegality(Point position)
        {
            if (!this.BoardModel[position].IsEmpty())
                return false;
            foreach (IntVector2 dir in BoardModel.Directions)
            {
                int distance = DistanceToNextPointInDirectionThatIs(position, dir, (TileState)this.BoardModel.CurrentState);
                if (distance < 2)
                    continue;
                if (NextNPointsInDirectionAre(position, distance - 1, dir, (TileState)this.BoardModel.CurrentState.Flipped()))
                    return true;
            }
            return false;
        }

        private void ensureScoreCorrectness()
        {
            this.RedPoints = 0;
            this.BluePoints = 0;
            for (int xC = 0; xC < decorations.GetLength(0); ++xC)
                for (int yC = 0; yC < decorations.GetLength(1); ++yC)
                {
                    if (this.BoardModel[xC, yC].HasFlag(TileState.Red))
                        this.RedPoints += 1;
                    else if (this.BoardModel[xC, yC].HasFlag(TileState.Blue))
                        this.BluePoints += 1;
                }
        }

        private void givePoints(PlayerColour player, int num)
        {
            if (player == PlayerColour.Red)
                RedPoints += num;
            else if (player == PlayerColour.Blue)
                BluePoints += num;
        }
        #endregion
    }
}
